﻿using Antlr.Runtime;
using System;
using System.Reflection;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices; // DllImport

namespace UnitTester
{
    public class Wrapper
    {
        //returning booleans does not seem to work
        //the return value is true when it should be false
        [DllImport("lexerWrapper.dll")]
        public static extern void initialize();
        [DllImport("lexerWrapper.dll")]
        public static extern int lexString([MarshalAs(UnmanagedType.LPStr)] string lexMe,
            [MarshalAs(UnmanagedType.LPStr)] StringBuilder tokenType, out int error);
    }

    class ComparativeTester
    {
        List<char> allowedChars;
        List<string> testStrings = new List<string>();
        ConstructorInfo lexerCtorInfo;
        Object lexerClassInfo;
        PropertyInfo lexerPropCharStream;
        Dictionary<string, MethodInfo> dmdToAntlrMethod = new Dictionary<string, MethodInfo>();
        int maxLength;
        public ComparativeTester()
        {
            string lexerClass = "MimicLexer";
            string assemblyName = "dlexer";
            Assembly ass = Assembly.Load(assemblyName);
            lexerClassInfo = ass.CreateInstance(lexerClass);
            lexerCtorInfo = lexerClassInfo.GetType().GetConstructor(new Type[] { typeof(ICharStream) });
            lexerPropCharStream = lexerClassInfo.GetType().GetProperty("CharStream");

            //initialize the thing to transform from dmd's names to antlr lexer rules
            Dictionary<string, string> dmdToAntlrType = new Dictionary<string, string>();
            dmdToAntlrType.Add("identifier", "mIDENTIFIER");
            dmdToAntlrType.Add("integer", "mINTEGERLITERAL");
            dmdToAntlrType.Add("float", "mFLOATLITERAL");
            dmdToAntlrType.Add("string", "mSTRINGLITERAL");
            dmdToAntlrType.Add("char", "mCHARACTERLITERAL");
            dmdToAntlrType.Add("error", "mTokens");

            foreach (string key in dmdToAntlrType.Keys)
            {
                dmdToAntlrMethod.Add(key, lexerClassInfo.GetType().GetMethod(dmdToAntlrType[key]));
            }

            maxLength = 5;
            enumerateAllowedChars(false, false, false, false, false, false, false, false, false, false, true);
            enumerateTests();
        }

        /// <summary>
        /// we add character to our pool that we choose from when randomly generating strings.
        /// turn on and off groups of characters that are used to make types of tokens using the doFoo
        /// parameters. I tried to overlap characters used in different tokens as much as possible. e.g. the
        /// letter 'b' is used in identifiers and hex digits and as a prefix for binary integers, so I used
        /// 'b' instead of 'a' in doIdentifier. This cuts down and the number of possibilites to test.
        /// </summary>
        private void enumerateAllowedChars(bool doIdentifier, bool doInteger, bool doDecimalFloat, bool doHexFloat, bool lString, bool lCharacter,
            bool doOperator, bool doPunctuation, bool doWhitespace, bool doEndOfLine, bool doComment)
        {
            HashSet<char> charSet = new HashSet<char>();
            //types of literals we are trying to make
            //identifier, integer,float, string, character
            //characters we allow
            if (doIdentifier)
            {
                //identifier
                //b _ 0 1
                charSet.Add('b');
                charSet.Add('_');
                charSet.Add('0');
                charSet.Add('1');
            }

            if (doInteger)
            {
                //integer   binary octal hex
                //0 1 L u b x _
                charSet.Add('0');
                charSet.Add('1');
                charSet.Add('L');
                charSet.Add('u');
                charSet.Add('b');
                charSet.Add('x');
                charSet.Add('_');
            }

            if (doDecimalFloat)
            {
                //float   decimal float
                //0 1 _ . e + f L i
                charSet.Add('0');
                charSet.Add('1');
                charSet.Add('_');
                charSet.Add('.');
                charSet.Add('e');
                charSet.Add('+');
                charSet.Add('f');
                charSet.Add('L');
                charSet.Add('i');
            }

            if (doHexFloat)
            {
                //float   decimal float   hex float
                //0 1 b _ . + x p f L i
                charSet.Add('0');
                charSet.Add('1');
                charSet.Add('b');
                charSet.Add('_');
                charSet.Add('.');
                charSet.Add('+');
                charSet.Add('x');
                charSet.Add('p');
                charSet.Add('f');
                charSet.Add('L');
                charSet.Add('i');
            }

            if (lString)
            {
                //string   wysiwig alternate wysiwig hex
                //r " ` c x
                charSet.Add('r');
                charSet.Add('"');
                charSet.Add('`');
                charSet.Add('c');
                charSet.Add('x');
            }

            if (lCharacter)
            {
                //character
                //' b
                charSet.Add('\'');
                charSet.Add('b');
            }

            if (doOperator)
            {
                // * / % = & - + < > ! ^ ~ ? :
                charSet.Add('*');
                charSet.Add('/');
                charSet.Add('%');
                charSet.Add('=');
                charSet.Add('&');
                charSet.Add('-');
                charSet.Add('+');
                charSet.Add('<');
                charSet.Add('>');
                charSet.Add('!');
                charSet.Add('^');
                charSet.Add('~');
            }
            if (doPunctuation)
            {
                // ? : . , ( ) { } [ ] $
                charSet.Add('?');
                charSet.Add(':');
                charSet.Add('.');
                charSet.Add(',');
                charSet.Add('(');
                charSet.Add(')');
                charSet.Add('{');
                charSet.Add('}');
                charSet.Add('[');
                charSet.Add(']');
                charSet.Add('$');
            }

            if (doWhitespace)
            { // space and \t
                charSet.Add(' ');
                charSet.Add('\t');
            }

            if (doEndOfLine)
            {
                charSet.Add('\r');
                charSet.Add('\n');
            }

            if (doComment)
            {
                // / * +
                charSet.Add('/');
                charSet.Add('*');
                charSet.Add('+');
            }

            //other chars to try
            //l to test float endings dont accept l
            //\ to test escape characters

            /* whats the minimum string length to exercise all possible paths?
             * integer: 5 0x1uL
             * float: 10 0x1.1p+1fi or maybe less: 8 0x.p+1fi
             * string: 5 r"a"d
             * character: 3 'a'
             * 
             * how many permutations is that all together?
             * 8^8 => 19,173,960
             * 8:45
             * 2,191,309 per minute
             * 
             * In depth testing of floats
             * 29 minutes to do 19,173,960 strings, but I prefixed them with 0x, so
             * they were mostly 10 characters
             * 
             * 17^6=> 25646166 doing everything besides floats
             * took 11:15
             */
            allowedChars = new List<char>(charSet);
            allowedChars.Sort();
            Trace.WriteLine("number of allowed characters= " + allowedChars.Count);
        }

        void enumerateTests()
        {
            Debug.Assert(Stopwatch.IsHighResolution);
            Stopwatch timer = Stopwatch.StartNew();
            StringBuilder testString = new StringBuilder(16);
            Trace.WriteLine("enumerating test strings");
            for (int length = 1; length <= maxLength; length++)
            {
                testString.Length = length;
                for (int counter = 0; counter < Math.Pow(allowedChars.Count, length); counter++)
                {
                    int temp = counter;
                    for (int index = 0; index < length; index++)
                    {
                        testString[index] = allowedChars[temp % allowedChars.Count];
                        temp /= allowedChars.Count;
                    }
                    testStrings.Add(testString.ToString());
                }
            }
            timer.Stop();
            Trace.WriteLine("elapsed time= " + timer.Elapsed.ToString());
            Console.WriteLine("number of test strings= " + testStrings.Count.ToString());
        }

        /// <summary>
        /// runs the input string through my lexer
        /// </summary>
        /// <param name="antlrMethod"></param>
        /// <param name="lexMe">the test token</param>
        /// <param name="error">out: 1 if there was an error parsing</param>
        /// <returns>how many characters were consumed</returns>
        int antlrLexString(MethodInfo antlrMethod, string lexMe, out int error)
        {
            error = 0;
            Object dlexerInstance = lexerCtorInfo.Invoke(new Object[] { new ANTLRStringStream(lexMe) });
            try
            {
                antlrMethod.Invoke(dlexerInstance, new object[] { });
            }
            catch (TargetInvocationException tie)
            {
                Debug.Assert(tie.InnerException.GetType().BaseType == typeof(RecognitionException));
                error = 1;
            }
            ICharStream cs = (ICharStream)lexerPropCharStream.GetValue(dlexerInstance, null);
            return cs.Index;
        }

        void runTests()
        {
            /* First run the test string past DMD's lexer. use the returned type to run the specific
             * rule of the lexer. make sure that errors match, and number of characters consumed match
             * if dmd says it's an error, run mTokens
             */
            Wrapper.initialize();
            StringBuilder dmdTokenType = new StringBuilder(32);
            int dmdError, antlrError;
            MethodInfo antlrMethod;
            Stopwatch timer = Stopwatch.StartNew();
            Stopwatch printTimer = Stopwatch.StartNew();
            testStrings.Insert(0, "/+a+/");
            testStrings.Insert(0, "/+a/++/b+/");
            testStrings.Insert(0, "/+a/+c+/b+/");
            testStrings.Insert(0, "/++///+/");
            testStrings.Insert(0, "/++//*/+*/");
            testStrings.Insert(0, "/+//+/");
            foreach (string s in this.testStrings)
            {
                int dmdCharsUsed = Wrapper.lexString(s, dmdTokenType, out dmdError);
                if (dmdError > 0)
                {
                    Trace.WriteLine("test token <" + s + "> is invalid");
                    antlrMethod = dmdToAntlrMethod["error"];
                }
                else
                {
                    Trace.WriteLine("test token <" + s + "> is valid");
                    /* TODO remove try get value
                      * it only is here because dmd has a name for punctuation and keywords, while i don't have
                      * a name for each one of them.
                      */
                    if (!dmdToAntlrMethod.TryGetValue(dmdTokenType.ToString(), out antlrMethod))
                    {
                        //get method mTokens, which tries to match them all
                        antlrMethod = dmdToAntlrMethod["error"];
                    }
                }
                int antlrCharsUsed = antlrLexString(antlrMethod, s, out antlrError);
                //bool bothAreZero = dmdError == 0 && antlrError == 0;
                //bool bothGTZero = dmdError > 0 && antlrError > 0;
                if ((dmdError == 0) != (antlrError == 0))
                {
                    //Console.WriteLine("IMPORTANT:");
                        Console.WriteLine("errors mismatched for test token <" + s + ">");
                }
                //apparently, if there's an error, the characters consumed may not match
                if (dmdError == 0 && dmdCharsUsed < antlrCharsUsed)
                    //if (dmdError == 0 && dmdCharsUsed != antlrCharsUsed) TODO restore this after line test
                {
                    //Console.WriteLine("IMPORTANT:");
                    Console.WriteLine("number of characters used mismatches for test token <" + s + ">");
                }
                if (printTimer.ElapsedMilliseconds > 1000 * 60)
                {
                    Console.WriteLine("One minute elapsed. Just tested <" + s + ">");
                    printTimer.Reset();
                    printTimer.Start();
                }
            }
            timer.Stop();
            Console.WriteLine("finished checking all " + testStrings.Count + " test strings");
            Console.WriteLine("it took this long= " + timer.Elapsed.ToString());
        }

        //static void Main(string[] args)
        //{
        //    ComparativeTester foo = new ComparativeTester();
        //    foo.runTests();
        //}
    }
}
